package com.sso.auth.config;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.sso.common.util.JwtUtil;
import com.sso.auth.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

import java.time.Duration;
import java.util.UUID;
import java.util.function.Function;

/**
 * OAuth2 授权服务器配置
 * 
 * @author SSO System
 * @since 2024-01-01
 */
@Configuration
@EnableWebSecurity
public class AuthorizationServerConfig {

    @Autowired
    private JwtUtil jwtUtil;
    
    @Autowired
    private CustomUserDetailsService userDetailsService;

    /**
     * OAuth2 授权服务器安全过滤器链
     */
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                .oidc(oidc -> oidc
                        .userInfoEndpoint(userInfo -> userInfo
                                .userInfoMapper(userInfoMapper())
                        )
                ); // 启用 OpenID Connect 1.0
        
        http
                // 重定向到登录页面，当未认证的用户尝试访问授权端点时
                .exceptionHandling(exceptions -> exceptions
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                )
                // 接受访问令牌进行用户信息和/或客户端注册
                .oauth2ResourceServer(resourceServer -> resourceServer
                        .jwt(Customizer.withDefaults()));

        return http.build();
    }

    /**
     * 默认安全过滤器链
     */
    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/login", "/error", "/webjars/**", "/css/**", "/js/**").permitAll()
                        .requestMatchers("/.well-known/**").permitAll() // 允许OIDC发现端点
                        .requestMatchers("/oauth2/jwks").permitAll() // 允许JWK端点
                        .anyRequest().authenticated()
                )
                // 表单登录处理从授权服务器过滤器链重定向而来的认证请求
                .formLogin(Customizer.withDefaults());

        return http.build();
    }

    /**
     * 注册客户端仓库
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        // OA 系统客户端
        RegisteredClient oaClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("oa-client")
                .clientSecret("{noop}oa-secret") // {noop} 表示不加密
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .redirectUri("http://www.oauth2server.com:8080/login/oauth2/code/oa-client")
                .redirectUri("http://www.oauth2server.com:8080/authorized")
                .postLogoutRedirectUri("http://www.oauth2server.com:8080/")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .scope("read")
                .scope("write")
                .clientSettings(ClientSettings.builder()
                        .requireAuthorizationConsent(false) // 移除授权确认以加快流程
                        .build())
                .tokenSettings(TokenSettings.builder()
                        .accessTokenTimeToLive(Duration.ofHours(1))
                        .refreshTokenTimeToLive(Duration.ofDays(7))
                        .build())
                .build();

        // 风控系统客户端
        RegisteredClient riskClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("risk-client")
                .clientSecret("{noop}risk-secret")
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .redirectUri("http://www.oauth2server.com:8081/login/oauth2/code/risk-client")
                .redirectUri("http://www.oauth2server.com:8081/authorized")
                .postLogoutRedirectUri("http://www.oauth2server.com:8081/")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .scope("read")
                .scope("write")
                .scope("risk:manage")
                .clientSettings(ClientSettings.builder()
                        .requireAuthorizationConsent(false) // 移除授权确认以加快流程
                        .build())
                .tokenSettings(TokenSettings.builder()
                        .accessTokenTimeToLive(Duration.ofHours(1))
                        .refreshTokenTimeToLive(Duration.ofDays(7))
                        .build())
                .build();

        // Portal门户系统客户端
        RegisteredClient portalClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("portal-client")
                .clientSecret("{noop}portal-secret")
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .redirectUri("http://www.oauth2server.com:8083/portal/login/oauth2/code/custom-client")
                .redirectUri("http://www.oauth2server.com:8083/portal/authorized")
                .postLogoutRedirectUri("http://www.oauth2server.com:8083/portal/")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .scope(OidcScopes.EMAIL)
                .scope("read")
                .scope("write")
                .clientSettings(ClientSettings.builder()
                        .requireAuthorizationConsent(false) // 移除授权确认以加快流程
                        .build())
                .tokenSettings(TokenSettings.builder()
                        .accessTokenTimeToLive(Duration.ofHours(1))
                        .refreshTokenTimeToLive(Duration.ofDays(7))
                        .build())
                .build();

        return new InMemoryRegisteredClientRepository(oaClient, riskClient, portalClient);
    }

    /**
     * JWK 源配置
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        JWKSet jwkSet = new JWKSet(jwtUtil.getRsaKey());
        return new ImmutableJWKSet<>(jwkSet);
    }

    /**
     * JWT 解码器
     */
    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    /**
     * 授权服务器设置
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
                .issuer("http://www.oauth2server.com:9000")
                .build();
    }
    
    /**
     * OIDC 用户信息映射器
     */
    private Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper() {
        return (context) -> {
            OidcUserInfoAuthenticationToken authentication = context.getAuthentication();
            String username = authentication.getName();
            
            // 从数据库获取用户信息
            com.sso.common.entity.User user = userDetailsService.getUserByUsername(username);
            
            if (user != null) {
                // 构建用户信息对象
                java.util.Map<String, Object> userInfoClaims = new java.util.HashMap<>();
                userInfoClaims.put("sub", user.getUsername());
                userInfoClaims.put("preferred_username", user.getUsername());
                userInfoClaims.put("name", user.getFullName());
                userInfoClaims.put("given_name", user.getFullName());
                userInfoClaims.put("family_name", "");
                userInfoClaims.put("email", user.getEmail());
                userInfoClaims.put("email_verified", true);
                userInfoClaims.put("phone_number", user.getPhone());
                userInfoClaims.put("phone_number_verified", true);
                userInfoClaims.put("profile", "");
                userInfoClaims.put("picture", "");
                userInfoClaims.put("website", "");
                userInfoClaims.put("gender", "");
                userInfoClaims.put("birthdate", "");
                userInfoClaims.put("zoneinfo", "Asia/Shanghai");
                userInfoClaims.put("locale", "zh-CN");
                userInfoClaims.put("updated_at", System.currentTimeMillis());
                
                // 添加自定义字段
                userInfoClaims.put("department", user.getDepartment());
                userInfoClaims.put("authorities", user.getAuthorities());
                userInfoClaims.put("id", user.getId());
                
                return new OidcUserInfo(userInfoClaims);
            }
            
            // 如果用户不存在，返回基本的用户信息
            java.util.Map<String, Object> defaultClaims = new java.util.HashMap<>();
            defaultClaims.put("sub", username);
            defaultClaims.put("preferred_username", username);
            return new OidcUserInfo(defaultClaims);
        };
    }
} 